#ifndef __bencoding_h_
#define __bencoding_h_

#include "config.h"
#include <sstream>
#include <vector>
#include <map>
#include <functional>
#include <memory>
#include "tiny_bt_exception.h"

namespace tiny_bt
{

class bd_exception :
	public tiny_bt_exception
{
	typedef tiny_bt_exception my_base;
public:
	bd_exception(const std::string& message):
		my_base(message){}
};

class bd_invalid_doc_exception :
	public bd_exception
{
	typedef bd_exception my_base;
public:
	bd_invalid_doc_exception(const std::string& message) :
		my_base(message){}

	bd_invalid_doc_exception() :
		my_base("invalid bencoding stream"){}
};

class bd_out_of_range :
	public bd_exception
{
	typedef bd_exception my_base;
public:
	bd_out_of_range(const std::string& message) :
		my_base(message) {}

	bd_out_of_range() :
		my_base("out range.") {}
};


class b_value;
class b_int;
class b_str;
class b_list;
class b_dic;

typedef std::shared_ptr<b_value> p_value;
typedef std::shared_ptr<b_int> p_int;
typedef std::shared_ptr<b_str> p_str;
typedef std::shared_ptr<b_list> p_list;
typedef std::shared_ptr<b_dic> p_dic;

class b_value : std::enable_shared_from_this<b_value>
{
public:
	virtual ~b_value(){}

	p_int to_int();

	p_list to_list();

	p_dic to_dic();

	p_str to_str();

	virtual std::ostream& write(std::ostream& o) = 0;
};

class b_int : public b_value
{
public:
	b_int(__int64 v) : value(v){}
	operator __int64() const
	{
		return value;
	}

	// Inherited via b_value
	virtual std::ostream & write(std::ostream & o) override;

private:
	__int64 value;
};

class b_str : public b_value
{
public:
	typedef std::vector<char> value_type;
	b_str(const value_type& v) : value(v){}
	b_str(value_type&& v) : value(move(v)){}

	operator std::string() const
	{
		std::string s(value.begin(), value.end());
		return s;
	}

	class alphabetical_less
	{
	public:
		bool operator()(const b_str& l, const b_str& r)
		{
			return l.operator std::string() < r.operator std::string();
		}
	};

	class equal
	{
	public:
		bool operator()(const b_str& l, const b_str& r)
		{
			if (l.value.size() != r.value.size())
			{
				return false;
			}

			if (l.value.empty())
			{
				return true;
			}

			return memcmp(&l.value[0], &r.value[0], l.value.size()) == 0;
		}
	};

	// Inherited via b_value
	virtual std::ostream & write(std::ostream & o) override;

private:
	value_type value;
};


class b_list : public b_value
{
public:
	typedef p_value node;
	typedef std::vector<node> value_type;
	typedef const node c_node;
	typedef value_type& value_ref;
	typedef const value_type& c_value_ref;
	typedef value_type::size_type size_type;

	b_list() = default;
	b_list(const b_list&) = delete;
	b_list(const b_list&&) = delete;

	auto size() const
	{
		return value.size();
	}

	bool empty() const
	{
		return value.empty();
	}

	c_node get(size_t i) const
	{
		if (size() <= i)
		{
			throw bd_out_of_range();
		}
		return value.at(i);
	}

	void attatch(node v)
	{
		value.push_back(v);
	}

	// Inherited via b_value
	virtual std::ostream & write(std::ostream & o) override;
private:
	value_type value;
};

class b_dic : public b_value
{
public:
	typedef std::pair<p_str, p_value> node;
	typedef std::vector<node> value_type;
	typedef std::vector<p_str> keys;
	typedef value_type& value_ref;
	typedef const value_type& c_value_ref;
	typedef value_type::size_type size_type;

	b_dic() = default;
	b_dic(const b_dic&) = delete;
	b_dic(const b_dic&&) = delete;

	auto get_keys()
	{
		keys r;
		for (const auto& n : value)
		{
			r.push_back(n.first);
		}
		return r;
	}

	auto get_value(const std::string& key)
	{
		for (const auto& n : value)
		{
			if (key == (const std::string&)n.first)
			{
				return n.second;
			}
		}
		throw bd_out_of_range(make_out_of_range_string(key));
	}

	void set_key_value(p_str k, p_value v)
	{
		for (auto& n : value)
		{
			if (b_str::equal()(*k, *n.first))
			{
				n.second = v;
				n.first = k;
				return;
			}
		}

		value.push_back(std::make_pair(k, v));
	}

	// Inherited via b_value
	virtual std::ostream & write(std::ostream & o) override;

private:
	std::string make_out_of_range_string(const std::string& key)
	{
		std::ostringstream oss;
		oss << "key not found in dict " << key;
		return oss.str();
	}
private:
	value_type value;
};

class b_reader
{
public:
	b_reader() = default;
	b_reader(const b_reader&) = delete;
	b_reader(const b_reader&&) = delete;
	p_value parse(std::istream& i);

private:
	p_int extract_int(std::istream& i);
	p_str extract_str(std::istream& i);
	p_dic extract_dic(std::istream& i);
	p_list extract_list(std::istream& i);
};

class b_writer
{
public:
	b_writer() = default;
	b_writer(const b_writer&) = delete;
	b_writer(const b_writer&&) = delete;
	void write(p_value b, std::ostream&o);
};

}

#endif //__bencoding_h_
